# = Class ASTExpression
# Inherits from AST. Represents the implementation of an AST especifically designed
# for expressions of the programming language Vectorinox
# == Attributes inherited from AST
# node, children
# == Methods inherited from AST
# check(), run()
class ASTExpression < AST

  # Type of the expression contained in the AST
  attr_reader :type
  # Value of the AST after performing eval
  attr_reader :value
  
  # Constructor of the class
  # === Parameters
  # * node: Token that represents the identifier for the operation represented by the AST
  # * children: AST of all the elements of the operation
  def initialize node, children
    @node = node
    @children = children
  end

  # Method to evaluate the expression
  # === Parameters
  # * parent_sym_table: Symbol table passed by the parent AST
  # * func_table: Function symbol table passed by the parent AST
  # === Return value
  # nil if executed with success or an error message
  def eval parent_sym_table, func_table

    if nil == @children
      # Tokens without fixed value
      case @node.class.name
      when 'TkVar'
        if nil == parent_sym_table.find(@node.value).value
            return "Variable no inicializada '#{@node.value}' (#{@node.to_s})"
        end
        sym_var = parent_sym_table.find(@node.value)
        @value = sym_var.value
        @type = sym_var.type
      when 'TkCnum'
        @value = @node.value
      when 'TkCString'
        @value = @node.value
      end
      return nil
    end

    # Only 1 or 2 children allowed
    case @children.length
    when 1
      msg = @children[0].eval parent_sym_table, func_table
      if nil != msg
        return msg
      end
    when 2
      msg = @children[0].eval parent_sym_table, func_table
      if nil != msg
        return msg
      end
      msg = @children[1].eval parent_sym_table, func_table
      if nil != msg
        return msg
      end
    end

    case @node.value
    when '+'
      # check ensures types of operands are equal
      if 'num' == @children[0].type
        @value = @children[0].value + @children[1].value
      elsif 'vec' == @children[0].type[0,3]
        @value = @children[1].value.add(@children[0].value)
        if @value.kind_of?String
          return @value + " (#{error_location})"
        end
      elsif 'mat' == @children[0].type
        @value = @children[0].value.add(@children[1].value)
        if @value.kind_of?String
          return @value + " (#{error_location})"
        end
      end
    when '-'
      #1 parameter, negative number. 2 paramters: subtraction
      if 2 == @children.length
        if 'num' == @children[0].type
          @value = @children[0].value - @children[1].value
        elsif 'vec' == @children[0].type[0,3]
          @value = @children[1].value.uminus.add(@children[0].value)
          if @value.kind_of?String
            return @value + " (#{error_location})"
          end
        else
          @value = @children[0].value.minus(@children[1].value)
          if @value.kind_of?String
            return @value + " (#{error_location})"
          end
        end
      else
        if 'num' == @children[0].type
          @value = - @children[0].value
        elsif 'vec' == @children[0].type[0,3] || 'mat' == @children[0].type
          @value = @children[0].value.uminus()
        end
      end
    when '*'
      if 'num' == @children[0].type
        if 'num' == @children[1].type
          @value = @children[0].value * children[1].value
        else
          @value = @children[1].value.prod(@children[0].value)
        end
      elsif 'vec' == @children[0].type[0,3]
        if 'num' == @children[1].type
          @value = @children[0].value.prod(@children[1].value)
        elsif 'vec' == @children[1].type[0,3]
          @value = @children[0].value.cross_prod(@children[1].value)
          if @value.kind_of?String
            return @value + " (#{error_location})"
          end
        elsif 'mat' == @children[1].type
          @value = @children[0].value.matrix.mat_prod(@children[1].value)
          if @value.kind_of?String
            return @value + " (#{error_location})"
          end
        end
      elsif 'mat' == @children[0].type
        if 'num' == @children[1].type
          @value = @children[0].value.prod(@children[1].value)
        else
          @value = @children[0].value.mat_prod(@children[1].value)
          if @value.kind_of?String
            return @value + " (#{error_location})"
          end
        end
      end
    when '.'
      @value = @children[0].value.scalar_prod(@children[1].value)
      if @value.kind_of?String
        return @value + " (#{error_location})"
      end
    when '%'
      if 0 == @children[1].value
          return "Tratando de dividir '#{@children[0].value}' por cero (#{@children[1].error_location})"
      end
      @value = @children[0].value % @children[1].value
    when '/'
      if 0 == @children[1].value
          return "Tratando de dividir '#{@children[0].value}' por cero (#{@children[1].error_location})"
      end
      if 'num' == @children[0].type
        @value = @children[0].value / @children[1].value
      else
        @value = @children[0].value.div(@children[1].value)
      end
    when '**'
      @value = @children[0].value ** @children[1].value
    when '$'
      @value = @children[0].value.size[0]
    when '@'
      @value = @children[0].value.size[1]
    when '^'
      if @children[0].type[0,3] != 'vec' && @children[0].type != 'mat'
          return "Tratando de trasponer '#{@children[0].type}' (#{@node.to_s})"
      end
      @value = @children[0].value.trasp
    end
    if (@value.kind_of?Matrix) || (@value.kind_of?Vector)
      @type = @value.type
    end

    nil
  end

  # Method to check the static correctitude of the expression
  # === Parameters
  # * parent_sym_table: Symbol table passed by the parent AST
  # * func_table: Function symbol table passed by the pare
  # * func_type: Type of valid return value
  # === Return value
  # nil if executed with success or an error message
  def check parent_sym_table, func_table, func_type
    if nil == @node
      return "Error interno: ASTExpression sin nodo (#{error_location})"
    end


    if nil == @children
      # Tokens without fixed value
      case @node.class.name
      when 'TkVar'
        if !parent_sym_table.find @node.value
            return "Variable no declarada '#{@node.value}' (#{@node.to_s})"
        end
        @type = parent_sym_table.find(@node.value).type
      when 'TkCnum'
        @type = 'num'
      when 'TkCString'
        @type = 'String'
      end
      return nil
    end

    # Only 1 or 2 children allowed
    case @children.length
    when 1
      msg = @children[0].check parent_sym_table, func_table, func_type
      if nil != msg
        return msg
      end
    when 2
      msg = @children[0].check parent_sym_table, func_table, func_type
      if nil != msg
        return msg
      end
      msg = @children[1].check parent_sym_table, func_table, func_type
      if nil != msg
        return msg
      end
    else
      return "Error interno: #{@children.length} operadores en ASTExpression (#{@node.to_s})"
    end

    case @node.value
    when '+'
      if children[0].type != 'num' && children[0].type != 'vec' && children[0].type != 'mat'
          return "Tratando de sumar '#{@children[0].type}' (#{@children[0].error_location})"
      end
      if ('num' == children[0].type || 'num' == children[1].type) && children[0].type != children[1].type
          return "Tratando de sumar tipos incompatibles '#{@children[0].type}'" +
                ", '#{@children[1].type}' (#{@children[0].error_location})"
      end
      @type = children[0].type
    when '-'
      #1 parameter, negative number. 2 paramters: subtraction
      if children[0].type != 'num' && children[0].type != 'vec' && children[0].type != 'mat'
          return "Tratando de " +
            (2 == children.length ? 'restarle a' : 'conseguir el opuesto de') +
            "'#{@children[0].type}' (#{@children[0].to_s})"
      end
      if 2 == children.length && children[0].type != children[1].type
          return "Tratando de restar '#{@children[1].type}' de '#{@children[0].type}' (#{@children[1].error_location})"
      end
      @type = children[0].type
    when '*'
      if children[0].type != 'num' && children[0].type != 'vec' && children[0].type != 'mat'
          return "Tratando de multiplicar '#{@children[0].type}' (#{@children[0].error_location})"
      end
      if children[1].type != 'num' && children[1].type != 'vec' && children[1].type != 'mat'
          return "Tratando de multiplicar '#{@children[1].type}' (#{@children[1].error_location})"
      end
      @type = 'num'
      if 'vec' == children[0].type || 'vec' == children[1].type
        @type = 'vec'
      end
      if 'mat' == children[0].type || 'mat' == children[1].type
        @type = 'mat'
      end
    when '.'
      if children[0].type != 'vec'
          return "Tratando de sacar el producto escalar a un '#{@children[0].type}' (#{@children[0].error_location})"
      end
      if children[1].type != 'vec'
          return "Tratando de sacar el producto escalar a un '#{@children[1].type}' (#{@children[1].error_location})"
      end
      @type = 'num'
    when '%'
      if children[0].type != 'num'
          return "Tratando de dividir '#{@children[0].type}' (#{@children[0].error_location})"
      end
      if children[1].type != 'num'
          return "Tratando de exponenciar a '#{@children[1].type}' (#{@children[1].error_location})"
      end
      @type = 'num'
    when '/'
      if children[0].type != 'num' && children[0].type != 'vec' && children[0].type != 'mat'
          return "Tratando de dividir '#{@children[0].type}' (#{@children[0].error_location})"
      end
      if children[1].type != 'num'
          return "Tratando de dividir por '#{@children[1].type}' (#{@children[1].error_location})"
      end
      @type = children[0].type
    when '**'
      if children[0].type != 'num'
          return "Tratando de exponenciar '#{@children[0].type}' (#{@children[0].error_location})"
      end
      if children[1].type != 'num'
          return "Tratando de exponenciar a '#{@children[1].type}' (#{@children[1].error_location})"
      end
      @type = 'num'
    when '$'
      if @children[0].type != 'vec' && @children[0].type != 'mat'
          return "Tratando de calcular el número de filas de '#{@children[0].type}' (#{@node.to_s})"
      end
      @type = 'num'
    when '@'
      if @children[0].type != 'vec' && @children[0].type != 'mat'
          return "Tratando de calcular el número de columnas de '#{@children[0].type}' (#{@node.to_s})"
      end
      @type = 'num'
    when '^'
      if @children[0].type != 'vec' && @children[0].type != 'mat'
          return "Tratando de trasponer '#{@children[0].type}' (#{@node.to_s})"
      end
      @type = @children[0].type
      # This CHANGES the vector type from vec_fil to vec_col and viceversa
    end

    nil
  end

  # Returns the closest wrong token to the place were an error is generated
  def error_tk
    if nil == @children
      if nil != @node
        return @node
      end
      return nil
    end
    if 2 == @children.length
      tk = @children[0].error_tk
    end
    if nil != tk
      return tk
    end
    tk = @node
    if nil != tk
      return tk
    end
    for e in @children
      tk = e.error_tk
      if nil != tk
        return tk
      end
    end
    nil
  end

  # Creates a 'String' representation of the AST with 'i' identation spaces
  # === Parameters
  # * i: Identation with which the AST should be printed with
  def to_s i
    im = i + @@c
    output = "(#{@node.class.name}"
    if @children == nil
      return "#{output}\n#{im}#{@node.value}\n#{i})"
    end
    output += to_s_children(im) + "\n#{i})"
  end
end